package com.hexus.eve.utils;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.PostConstruct;

import org.apache.log4j.Logger;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.hexus.eve.entities.MarketGroup;

/**
 * Singleton utility class for MarketGroup operations.
 * Stores MarketGroup tree, provides all necessary functions to work with this tree.
 * @author Mikhail_Berastau
 */

@SuppressWarnings("restriction")
@Service
public class MarketGroupUtils {
	private Logger log = Logger.getLogger(this.getClass());
	
	@Autowired
	private SessionFactory sessionFactory;
	
	public static final Boolean PARENTS = true;
	public static final Boolean CHILDREN = false;
	
	private static Map<Integer,Set<Integer>> children = new HashMap<Integer, Set<Integer>>();
	private static Map<Integer,Set<Integer>> parents = new HashMap<Integer, Set<Integer>>();
	private static Map<Integer, MarketGroup> marketGroups = new HashMap<Integer, MarketGroup>();
	
	
	private static volatile MarketGroupUtils instance;
	
	public static MarketGroupUtils getInstance(){
		if (instance == null) {
			synchronized (MarketGroupUtils.class) {
				instance = new MarketGroupUtils();	
			}
		} 
		return instance;
	}
	

	private MarketGroupUtils() {}
	
	@SuppressWarnings({ "unused" })
	@PostConstruct
	private void init() {
		Date timer = new Date();
		if(log.isDebugEnabled()) {
			log.debug("Initializing MarketGroups bean...");
		}
		Session session = sessionFactory.openSession();
		try {
			@SuppressWarnings("rawtypes")
			Iterator iter = session.createQuery("from MarketGroup").iterate();
			while(iter.hasNext()) {
				
				MarketGroup mg = (MarketGroup)iter.next();
				marketGroups.put(mg.getId(), mg);
				
				Integer child = mg.getId();
				Integer parent = mg.getParentId();
				
				if(!children.containsKey(parent)){
					children.put(parent, new HashSet<Integer>());
				}
				children.get(parent).add(child);
				
				if(!parents.containsKey(child)){
					parents.put(child, new HashSet<Integer>());
				}
				parents.get(child).add(parent);
			}
			
			if(log.isDebugEnabled()) {
				log.debug("The MarketGroups bean is initialized. " + marketGroups.size() + " groups are loaded in " + ((new Date()).getTime() - timer.getTime()) / 1000 + " seconds.");
			}

		}
		catch(Exception ex) {
			Logger logger = Logger.getLogger(this.getClass().getName());
			logger.error(ex.getMessage());
			ex.printStackTrace();
		}
		finally {
			session.close();
		}
	}
	
	/**
	 * Returns clone (because it can not be modifed from outside, e.g. via translation)
	 * of MarketGroup object by its ID
	 * 
	 * @param id ID of an item to be returned
	 * @return MarketGroup object with corresponding ID
	 */
	public MarketGroup getMarketGroup(Integer id) {
		/*
		MarketGroup mg = null;
		Session session = sessionFactory.openSession();
		try {
			mg = (MarketGroup) session.load(MarketGroup.class, id);
			session.lock(mg, LockMode.NONE);
		} catch (HibernateException he) {
			System.out.println(he.getMessage());
			he.printStackTrace();
		}
		session.close();
		return mg;
		*/
		MarketGroup mg = marketGroups.get(id);
		if(mg != null)
			mg = mg.clone();
		return mg;
	}
	
	/**
	 * Returns list of MarketGroup objects by their IDs
	 * @param ids List of IDs to be converted to MarketGroup objects list
	 */
	public List<MarketGroup> getMarketGroups(List<Integer> ids) {
		List<MarketGroup> list = new ArrayList<MarketGroup>();
		for(Integer i: ids ) {
			MarketGroup mg = getMarketGroup(i);
			if(mg != null)
				list.add(mg);
		}
		
		return list;
	}
	
	private void addIdsToList(Integer id, List<Integer> idsList, Boolean isLookingForParent){
		Set<Integer> ids = isLookingForParent ? parents.get(id) : children.get(id);
		if(ids != null) {
			Iterator<Integer> it = ids.iterator();
			while(it.hasNext()) {
				Integer currentId = it.next();
				if(currentId != null) {
					idsList.add(currentId);
					addIdsToList(currentId, idsList, isLookingForParent);
				}
			}		
		}
	}	
	
	/**
	 * Returns list with IDs of all parents/children of an item
	 * @param id ID of the item which parents/children IDs need to be found
	 * @param isLookingForParent defines whether it should work with parents or children set
	 */
	public List<Integer> getAllRelatedItemsIds(Integer id, Boolean isLookingForParent){
		List<Integer> relatedItemsIdsList = new ArrayList<Integer>();
		addIdsToList(id, relatedItemsIdsList, isLookingForParent);
		return relatedItemsIdsList;
	}
	
	/**
	 * Returns list with IDs of all parents/children of an item
	 * @param mg Item which parents/children IDs need to be found
	 * @param isLookingForParent defines whether it should work with parents or children set
	 */	
	public List<Integer> getAllRelatedItemsIds(MarketGroup mg, Boolean isLookingForParent){
		return getAllRelatedItemsIds(mg.getId(), isLookingForParent);
	}
	

	/**
	 * Returns list with all parents/children of an item
	 * @param id ID of the item which parents/children need to be found
	 * @param isLookingForParent defines whether it should work with parents or children set
	 */
	public List<MarketGroup> getAllRelatedItems(Integer id, Boolean isLookingForParent){
		List<Integer> relatedItemsIdsList = new ArrayList<Integer>();
		addIdsToList(id, relatedItemsIdsList, isLookingForParent);
		return getMarketGroups(relatedItemsIdsList);
	}
	
	/**
	 * Returns list with all parents/children of an item
	 * @param mg Item which parents/children need to be found
	 * @param isLookingForParent defines whether it should work with parents or children set
	 */
	public List<MarketGroup> getAllRelatedItems(MarketGroup mg, Boolean isLookingForParent){
		return getAllRelatedItems(mg.getId(), isLookingForParent);
	}
	
	/**
	 * Returns list with first level parents/children of an item
	 * @param id ID of the item which parents/children need to be found
	 * @param isLookingForParent defines whether it should work with parents or children set
	 */
	public List<MarketGroup> getRelatedItems(Integer id, Boolean isLookingForParent){
		List<Integer> idsList = new ArrayList<Integer>();
		Set<Integer> ids = isLookingForParent ? parents.get(id) : children.get(id);
		if(ids != null) {
			Iterator<Integer> it = ids.iterator();
			while(it.hasNext()) {
				Integer currentId = it.next();
				if(currentId != null)
					idsList.add(currentId);				
			}		
		}
		
		return getMarketGroups(idsList); 
	}
	
	/**
	 * Returns list with first level parents/children of an item
	 * @param mg Item which parents/children need to be found
	 * @param isLookingForParent
	 * @return
	 */
	public List<MarketGroup> getRelatedItems(MarketGroup mg, Boolean isLookingForParent){
		return getRelatedItems(mg.getId(), isLookingForParent);
	}
	
}
